﻿#region Copyright

// // The contents of this file are subject to the Mozilla Public License
// // Version 1.1 (the "License"); you may not use this file except in compliance
// // with the License. You may obtain a copy of the License at
// //   
// // http://www.mozilla.org/MPL/
// //   
// // Software distributed under the License is distributed on an "AS IS"
// // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// // License for the specific language governing rights and limitations under 
// // the License.
// //   
// // The Initial Developer of the Original Code is Robert Smyth.
// // Portions created by Robert Smyth are Copyright (C) 2008-2013.
// //   
// // All Rights Reserved.

#endregion

using System;
using NLog;
using NoeticTools.NWorkflow.DotNetInterop;
using NoeticTools.NWorkflow.Exceptions;
using NoeticTools.NWorkflow.ProgressMonitoring;
using NoeticTools.NWorkflow.Steps;


namespace NoeticTools.NWorkflow
{
    internal class Workflow<T> : IWorkflowRunner<T>, IWorkflow<T>
    {
        private readonly Logger _logger;
        private readonly NullStep _nullStep = new NullStep();
        private readonly WorkflowProgressMonitor<T> _workflowProgress;
        private readonly string _name;
        private readonly IWorkflowRules<T> _rules;
        private IActivityListener<T> _listener;

        public Workflow(string name, IWorkflowRules<T> rules, ITimerFactory timerFactory = null)
        {
            _name = name;
            _rules = rules;
            TimerFactory = timerFactory ?? new DotNetTimerFactory();
            SyncRoot = new object();
            CurrentStep = _nullStep;
            _workflowProgress = new WorkflowProgressMonitor<T>(_rules.Progress);
            _logger = LogManager.GetLogger(string.Format("NWorkflow.Workflow({0})", _name.Replace(' ', '_')));
        }

        public T Result { get; private set; }

        public void FireEvent(object eventValue)
        {
            CurrentStep.OnEventFired(eventValue);
        }

        public void Begin(IActivityListener<T> listener)
        {
            lock (SyncRoot)
            {
                if (!ReferenceEquals(_nullStep, CurrentStep))
                {
                    throw new WorkflowAlreadyStartedException();
                }

                _rules.Validate();
                Progress.UpdateWeights(Progress.StartingWeight);

                _listener = listener;

                _rules.OnBegin.Run();
            }
        }

        /// <summary>
        ///     Cancel the workflow avoiding any further notifications.
        /// </summary>
        public void Cancel()
        {
            lock (SyncRoot)
            {
                _rules.Stop();

                CurrentStep.Abort();
                CurrentStep = _nullStep;
            }
        }

        public void OnEvent(object value)
        {
            FireEvent(value);
        }

        public IWorkflow<T> AddPrecondition<TCondition>(IActivity<TCondition> activity, TCondition grantValue, T onDeniedValue)
        {
            return new ConditionalRunWorkFlowRules<TCondition, T>(activity, this, grantValue, onDeniedValue).CreateWorkflow("", TimerFactory);
        }

        public IWorkflow<T> DecorateWith(IActivity<T> activity, IActivityListener<T> listener)
        {
            return new WorkFlowRulesActivityDecorator<T>(activity, this, listener).CreateWorkflow("", TimerFactory);
        }

        public T End()
        {
            Cancel();
            return Result;
        }

        public IProgress Progress
        {
            get { return _workflowProgress; }
        }

        public ITimerFactory TimerFactory { get; private set; }

        public object SyncRoot { get; private set; }

        public void Exit(T result)
        {
            lock (SyncRoot)
            {
                CurrentStep.Leave();
                CurrentStep = _nullStep;
                Result = result;
            }
            _listener.OnCompleted(this);
        }

        public void TransitionTo(string destinationStep)
        {
            _logger.Debug("Transition to '{0}'.", destinationStep);

            IStep nextStep = null;
            lock (SyncRoot)
            {
                CurrentStep.Leave();
                CurrentStep = _rules.GetStep(destinationStep, TimerFactory.Create(destinationStep));
                nextStep = CurrentStep;
                Progress.Inner = CurrentStep.Progress;
            }
            nextStep.Enter();
        }

        void IActivityListener<T>.OnProgress(IActivityProgress progress)
        {
            var progressInfo = new ProgressInfo() { Completed = progress.Completed, Message = progress.Message };

            CurrentStep.Progress.Inner = progressInfo;
            CurrentStep.Progress.Completed = progress.Completed;
        }

        void IActivityListener<T>.OnCompleted(IActivity<T> activity)
        {
        }

        void IStepProgressListener.OnProgress(string stepName, IProgress stepProgress)
        {
            if (!string.Equals(CurrentStep.Name, stepName, StringComparison.InvariantCulture))
            {
                // progress from activity within current step ... ought to report to the step
                _workflowProgress.Inner = CurrentStep.Progress;
                CurrentStep.Progress.Inner = stepProgress;
                CurrentStep.Progress.Completed = stepProgress.Completed;
            }
            else
            {
                _workflowProgress.Inner = stepProgress;
            }

            var totalCompleted = _rules.Progress.TotalCompleted;
            if (totalCompleted > 0.0)
            {
                _workflowProgress.Completed = totalCompleted/Progress.Total;
            }

            _listener.OnProgress(stepName, _workflowProgress);
        }

        public IStep CurrentStep { get; set; }
    }
}